/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.util;

import java.util.Arrays;

import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Value;

/**
 * This class implements a Cadmium stack, based on an array. <br/>
 * This implementation is <b>NOT</b> synchronized. Hence, use by multiple
 * threads may result in contract violation (<i>e.g.</i> stack may contain
 * more elements than specified by maximum size).
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
public final class ArrayStack implements Stack {

    /** Index of top element (<tt>-1</tt> when empty). */
    private int top;

    /** Stack elements. */
    private final Value[] elements;

    /** Maximum index of stack elements. */
    private final int maxIndex;

    /**
     * Constructs an empty stack.
     * @param maxSz maximum stack size - should be strictly positive
     */
    public ArrayStack(final int maxSz) {
        assert maxSz > 0 : "maxSz should be > 0";
        this.elements = new Value[maxSz];
        this.top = -1;
        this.maxIndex = maxSz - 1;
    } // end constructor(int)

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        return this.top == -1;
    } // end method 'isEmpty()'

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isFull() {
        return this.top >= this.maxIndex;
    } // end method 'isFull()'

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return this.top + 1;
    } // end method 'size()'

    /**
     * {@inheritDoc}
     */
    @Override
    public int maxSize() {
        return this.maxIndex + 1;
    } // end method 'maxSize()'

    /**
     * {@inheritDoc}
     */
    @Override
    public void push(final Value v) throws Fail.Exception {
        assert v != null : "null v";
        if (isFull()) {
            Fail.raiseStackOverflow();
        } else {
            this.elements[++this.top] = v;
        } // end if/else
    } // end method 'push(Value)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value pop() throws Fatal.Exception {
        if (isEmpty()) {
            Fatal.raiseStackUnderflow();
            return null; // never reached
        } else {
            final Value res = this.elements[this.top];
            this.elements[this.top--] = null;
            return res;
        } // end if/else
    } // end method 'pop()'

    /**
     * {@inheritDoc}
     */
    @Override
    public void pop(final int n) throws Fatal.Exception {
        assert n >= 0 : "n should be >= 0";
        if (size() >= n) {
            for (int i = 0; i < n; i++) {
                this.elements[this.top--] = null;
            } // end for
        } else {
            while (this.top > -1) {
                this.elements[this.top--] = null;
            } // end while
            Fatal.raiseStackUnderflow();
        } // end if/else
    } // end method 'pop(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value peek(final int idx) throws Fatal.Exception {
        assert idx >= 0 : "idx should be >= 0";
        if (idx < size()) {
            return this.elements[this.top - idx];
        } else {
            Fatal.raiseStackUnderflow();
            return null; // never reached
        } // end if/else
    } // end method 'peek(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void assign(final int idx, final Value v) throws Fatal.Exception {
        assert idx >= 0 : "idx should be >= 0";
        assert v != null : "null v";
        if (idx < size()) {
            this.elements[this.top - idx] = v;
        } else {
            Fatal.raiseStackUnderflow();
        } // end if/else
    } // end method 'assign(int, Value)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value[] popSlice(final int n) throws Fatal.Exception {
        assert n >= 0 : "n should be >= 0";
        if (size() >= n) {
            final Value[] res = new Value[n];
            System.arraycopy(this.elements, this.top - n + 1, res, 0, n);
            pop(n);
            return res;
        } else {
            pop(n);
            Fatal.raiseStackUnderflow();
            return new Value[0]; // never reached
        } // end if/else
    } // end method 'popSlice(int)'

    /**
     * {@inheritDoc} <br/>
     * <b>Does not check that <tt>s</tt> elements are not null.</b>
     */
    @Override
    public void pushSlice(final Value[] s) throws Fail.Exception {
        assert s != null : "null s";
        final int len = s.length;
        if (this.top + len > this.maxIndex) {
            System.arraycopy(s, 0, this.elements, this.top + 1, this.maxIndex - this.top);
            this.top = this.maxIndex;
            Fail.raiseStackOverflow();
        } else {
            System.arraycopy(s, 0, this.elements, this.top + 1, len);
            this.top += len;
        } // end if/else
    } // end method 'pushSlice(Value[])'

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ArrayStack [maxSize=");
        sb.append(this.maxIndex + 1);
        sb.append(", top=");
        sb.append(this.top);
        sb.append("]: ");
        sb.append(Arrays.toString(this.elements));
        return sb.toString();
    } // end method 'toString()'

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof ArrayStack) {
            final ArrayStack that = (ArrayStack) obj;
            return (this.maxIndex == that.maxIndex)
                && Arrays.equals(this.elements, that.elements);
        } else {
            return false;
        } // end if/else
    } // end method 'equals(Object)'

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.maxIndex + Arrays.hashCode(this.elements);
    } // end method 'hashCode()'

} // end class 'ArrayStack'
